using System;
using System.Text;
using System.Collections;
using System.Net;
using System.IO;
using System.Configuration;

/*
 * Direct reflection of the javascript google maps API.
 * Tries to stay as consistent as possible down to
 * syntax and case.
 */
namespace Maps
{

	// Direct interface into the javascript Map.
	public class Map : EventHandler {

		/*	Constructors */
		public Map(string elementID) : this(elementID, null) {
		}

		public Map(string elementID, MapOptions options) {
			this.elementID = elementID;

			if(options != null) {
				this.mapTypes = options.mapTypes;
				this.size = options.size;
				this.draggableCursor = options.draggableCursor;
				this.draggingCursor = options.draggingCursor;
			}
		}

		/*	Data */
		private string elementID;

		/*	Settings */
		private string draggableCursor = "";
		private string draggingCursor = "";
		private bool draggingSetting = true;
		private bool infoWindowSetting = true;
		private bool doubleClickZoomSetting = false;
		private bool continuousZoomSetting = false;

		public string getDraggableCursor() {
			return draggableCursor;
		}
		public string getDraggingCursor() {
			return draggingCursor;
		}
		public void enableDragging() {
			draggingSetting = true;
		}
		public void disableDragging() {
			draggingSetting = false;
		}
		public bool draggingEnabled() {
			return draggingSetting;
		}
		public void enableInfoWindow() {
			infoWindowSetting = true;
		}
		public void disableInfoWindow() {
			infoWindowSetting = false;
		}
		public bool infoWindowEnabled() {
			return infoWindowSetting;
		}
		public void enableDoubleClickZoom() {
			doubleClickZoomSetting = true;
		}
		public void disableDoubleClickZoom() {
			doubleClickZoomSetting = false;
		}
		public bool doubleClickZoomEnabled() {
			return doubleClickZoomSetting;
		}
		public void enableContinuousZoom() {
			continuousZoomSetting = true;
		}
		public void disableContinuousZoom() {
			continuousZoomSetting = false;
		}
		public bool continuousZoomEnabled() {
			return continuousZoomSetting;
		}


		/*	Controls */
		private ArrayList controls = new ArrayList();
		public void addControl(Control control) {
			addControl(control, null);
		}

		public void addControl(Control control, ControlPosition position) {
			if(position != null) {
				control.setPosition(position);
			}
			else {
				control.setPosition(control.getDefaultPosition());
			}

			if(!controls.Contains(controls))
				controls.Add(control);
		}

		public void removeControl(Control control) {
			controls.Remove(control);
		}

		public ArrayList getControls() {
			return controls;
		}

		public string getContainer() {
			return elementID;
		}

		/*	MapTypes */
		private ArrayList mapTypes = new ArrayList();
		private MapType currentMapType;
		public ArrayList getMapTypes() {
			return mapTypes;
		}

		public MapType getCurrentMapType() {
			return currentMapType;
		}

		public void setMapType(MapType type) {
			if(!mapTypes.Contains(type)) {
				mapTypes.Add(type);
			}
			currentMapType = type;
		}

		public void addMapType(MapType type) {
			if(type == MapType.G_DEFAULT_MAP_TYPES) {
				mapTypes.Clear();
				mapTypes.Add(type);
			}
			else if(!mapTypes.Contains(type)) {
				mapTypes.Add(type);
			}
		}

		public void removeMapType(MapType type) {
			mapTypes.Remove(type);
		}

		/*	Map State */
		private LatLng center;
		private LatLngBounds bounds = null;  // here for consistency, never used though (not implemented compile-time)
		private Size size;
		private int zoom = 0;
		public bool isLoaded() {
			return (center!=null);
		}

		public LatLng getCenter() {
			return center;
		}

		public LatLngBounds getBounds() {
			return bounds;
		}

		public int getBoundsZoomLevel(LatLngBounds bounds) {
			throw new NotImplementedException("getBoundsZoomLevel is not implemented in compile time in C# google maps");
		}

		public Size getSize() {
			return size;
		}

		public int getZoom() {
			return zoom;
		}

		public void setCenter(LatLng center) {
			setCenter(center, 0, MapType.G_INVALID_MAP);
		}
		public void setCenter(LatLng center, int zoom) {
			setCenter(center, zoom, MapType.G_INVALID_MAP);
		}
		public void setCenter(LatLng center, int zoom, MapType mapType) {
			this.center = center;
			this.zoom = zoom;
			if(mapType != MapType.G_INVALID_MAP) {
				setMapType(mapType);
			}
		}

		public void panTo(LatLng center) {
			throw new NotImplementedException("panTo is not implemented at compile time in C# google maps");
		}

		public void panBy(int distance) {
			throw new NotImplementedException("panBy is not implemented at compile time in C# google maps");
		}

		public void panDirection(int dx, int dy) {
			throw new NotImplementedException("panDirection is not implemented at compile time in C# google maps");
		}

		public void setZoom(int level) {
			this.zoom = level;
		}

		public void zoomIn() {
			this.zoom++;
		}

		public void zoomOut() {
			this.zoom--;
		}

		public void savePosition() {
			throw new NotImplementedException("savePosition is not implemented at compile time in C# google maps");
		}

		public void returnToSavedPosition() {
			throw new NotImplementedException("savePosition is not implemented at compile time in C# google maps");
		}

		public void checkResize() {
			throw new NotImplementedException("checkResize is not implemented at compile time in C# google maps");
		}


		/*	Overlays */
		private ArrayList overlays = new ArrayList();
		public void addOverlay(Overlay overlay) {
			overlays.Add(overlay);
		}
		public void addOverlays(ArrayList overlayList) {
			foreach(Overlay o in overlayList) {
				overlays.Add(o);
			}
		}
		public void removeOverlay(Overlay overlay) {
			overlays.Remove(overlay);
		}
		public void clearOverlays() {
			overlays.Clear();
		}
		public ArrayList getOverlays() {
			return overlays;
		}

		public void getPane(string pane) {
			throw new NotImplementedException("getPane is not implemented at compile time in C# google maps");
		}

	}

	public enum MapType {
		G_INVALID_MAP=0,
		G_NORMAL_MAP,
		G_SATELLITE_MAP,
		G_HYBRID_MAP,
		G_DEFAULT_MAP_TYPES
	}

	public class MapOptions {
		public Size size = new Size(-1, -1);
		public ArrayList mapTypes = null;
		public string draggableCursor = "";
		public string draggingCursor = "";

		public MapOptions() {
		}
	}

	public enum ControlAnchor {
		G_ANCHOR_TOP_RIGHT = 1,
		G_ANCHOR_TOP_LEFT,
		G_ANCHOR_BOTTOM_RIGHT,
		G_ANCHOR_BOTTOM_LEFT
	}

	public class ControlPosition {
		public ControlAnchor anchor;
		public Size offset;

		public ControlPosition(ControlAnchor anchor, Size offset) {
			this.anchor = anchor;
			this.offset = offset;
		}
	}

	public class Control {
		public Control() : this(true, true) {
		}
		public Control(bool printable, bool selectable) {
			this.printableSetting = printable;
			this.selectableSetting = selectable;
		}

		private bool printableSetting;
		private bool selectableSetting;
		private string jsInitializeScript;
		private ControlPosition position;

		public bool printable() {
			return printableSetting;
		}

		public bool selectable() {
			return selectableSetting;
		}

		public void setInitializeScript(string js) {
			jsInitializeScript = js;
		}

		public string getInitializeScript() {
			return jsInitializeScript;
		}

		public virtual ControlPosition getDefaultPosition() {
			return new ControlPosition(ControlAnchor.G_ANCHOR_TOP_RIGHT, new Size(0,0));
		}
	
		public ControlPosition getPosition() {
			return position;
		}
		public void setPosition(ControlPosition p) {
			position = p;
		}
	}

	public class LargeMapControl : Control {
		public LargeMapControl() {
		}
	}
	public class SmallMapControl : Control {
		public SmallMapControl() {
		}
	}
	public class SmallZoomControl : Control {
		public SmallZoomControl() {
		}
	}
	public class MapTypeControl : Control {
		public MapTypeControl() {
		}
	}

	public class LatLng {
		public LatLng(double lat, double lng) : this(lat, lng, false) {
		}
		public LatLng(double lat, double lng, bool unbounded) {
			if(!unbounded) {
				if(lat < -90) lat = -90;
				else if(lat > 90) lat = 90;

				if(lng < -180) lng = -180;
				else if(lng > 180) lng = 180;
			}

			this.latitude = lat;
			this.longitude = lng;
		}

		private double latitude;
		private double longitude;

		public double lat() {
			return latitude;
		}

		public double lng() {
			return longitude;
		}

		public double latRadians() {
			return latitude/180.0F*3.14159265F;
		}

		public double lngRadians() {
			return longitude/180.0F*3.14159265F;
		}

		public bool equals(LatLng other) {
			bool ret = false;
			if(other.latitude == this.latitude && other.longitude == this.longitude)
				ret = true;
			return ret;
		}

		public override string ToString() {
			return latitude + "," + longitude;
		}

		public string toString() {
			return ToString();
		}

		public string toUrlValue() {
			return ToString();
		}
	}

	public class Overlay : EventHandler {
		public Overlay() {
		}

		public int getZIndex(double latitude) {
			throw new NotImplementedException("getZIndex is not implemented at compile time in C# google maps");
		}

		public virtual bool isValid() {
			return true;
		}
	}

	public class Marker : Overlay {
		public Marker(LatLng point) {
			this.point = point;
		}
		public Marker(LatLng point, Icon icon) {
			this.point = point;
			this.opts.icon = icon;
		}
		public Marker(LatLng point, MarkerOptions opts) {
			this.point = point;
			this.opts = opts;
		}
		public Marker(string address) : this(address, false) {
		}
		public Marker(string address, Icon icon) : this(address, icon, false) {
		}
		public Marker(string address, MarkerOptions opts) : this(address, opts, false) {
		}
		public Marker(string address, bool geocodeFlag) {
			this.address = address;
			if(geocodeFlag) geocode();
		}
		public Marker(string address, Icon icon, bool geocodeFlag) {
			this.address = address;
			this.opts.icon = icon;
			if(geocodeFlag) geocode();
		}
		public Marker(string address, MarkerOptions opts, bool geocodeFlag) {
			this.address = address;
			this.opts = opts;
			if(geocodeFlag) geocode();
		}

		private LatLng point;
		private string address;

		private bool dragging;
		private string foregroundImage;
		private MarkerOptions opts = new MarkerOptions();

		public override bool isValid() {
			return (point != null);
		}

		public void openInfoWindow(string contentID, InfoWindowOptions opts) {
			throw new NotImplementedException("openInfoWindow is not implemented at compile time in C# google maps");
		}
		public void openInfoWindowHtml(string html, InfoWindowOptions opts) {
			throw new NotImplementedException("openInfoWindowHtml is not implemented at compile time in C# google maps");
		}
		public void openInfoWindowTabs(string tabs, InfoWindowOptions opts) {
			throw new NotImplementedException("openInfoWindowTabs is not implemented at compile time in C# google maps");
		}
		public void openInfoWindowTabsHtml(string tabs, InfoWindowOptions opts) {
			throw new NotImplementedException("openInfoWindowTabsHtml is not implemented at compile time in C# google maps");
		}
		public void showMapBlowup(InfoWindowOptions opts) {
			throw new NotImplementedException("showMapBlowup is not implemented at compile time in C# google maps");
		}

		/*	Look up the address */
		public void geocode() {
			WebRequest request = WebRequest.Create("http://maps.google.com/maps/geo?q=" + getAddress() + "&key=" + ConfigurationSettings.AppSettings["google_maps_key"] + "&output=csv");
			HttpWebResponse response = (HttpWebResponse)request.GetResponse();
			StreamReader reader = new StreamReader(response.GetResponseStream());
				
			string[] responseArr = reader.ReadToEnd().Split(',');
			string latLng = responseArr[2] + "," + responseArr[3];

			if(responseArr[0] == "200") {
				setPoint(new LatLng(Double.Parse(responseArr[2]), Double.Parse(responseArr[3])));
			}
		}

		/*	Info windows
		 *	
		 *	There are 3 methods of adding info windows:
		 *		- Just adding content for a basic window
		 *		- Adding separate tabs to be used be the default info window
		 *		- Adding a custom overlay
		 * 
		 * */
		private string infoWindowContent;
		public void setInfoWindowContent(string content) {
			infoWindowContent = content;
		}
		public string getInfoWindowContent() {
			return infoWindowContent;
		}

		private ArrayList infoTabs = new ArrayList();
		public void addInfoWindowTab(InfoWindowTab tab) {
			infoTabs.Add(tab);
		}
		public ArrayList getInfoWindowTabs() {
			return infoTabs;
		}

		private Overlay infoWindow;
		public void addCustomInfoWindow(Overlay window) {
			infoWindow = window;
		}
		public Overlay getCustomInfoWindow() {
			return infoWindow;
		}

		/*	Settings */
		public Icon getIcon() {
			return opts.icon;
		}

		public LatLng getPoint() {
			return point;
		}
		public void setPoint(LatLng point) {
			this.point = point;
		}

		public string getAddress() {
			return address;
		}
		public void setAddress(string address) {
			this.address = address;
		} 

		public void enableDragging() {
			if(opts.draggable) dragging = true;
		}
		public void disableDragging() {
			if(opts.draggable) dragging = false;
		}
		public bool draggable() {
			return opts.draggable;
		}
		public bool draggingEnabled() {
			return dragging;
		}

		public void setImage(string url) {
			foregroundImage = url;
		}
		public string getImage() {
			return foregroundImage;
		}

		public MarkerOptions getOpts() {
			return this.opts;
		}

		public override string ToString() {
			return "Address: " + getAddress() + " Point: " + (getPoint() == null ? "null" : getPoint().ToString());
		}
	}

	public class MarkerOptions {
		public MarkerOptions() {
			icon = Icon.G_DEFAULT_ICON;
			dragCrossMove = false;
			title = "";
			clickable = true;
			draggable = false;
			bouncy = false;
			bounceGravity = 1;
		}

		public Icon icon;
		public bool dragCrossMove;
		public string title;
		public bool clickable;
		public bool draggable;
		public bool bouncy;
		public int bounceGravity;
	}

	public class MarkerManager {
		public MarkerManager() {
		}
		public MarkerManager(MarkerManagerOptions opts) : this() {
			this.opts = opts;
		}

		private MarkerManagerOptions opts = new MarkerManagerOptions();

		public class MarkerBatch {
			public MarkerBatch(ArrayList l) :this(l, 0, -1) {
			}
			public MarkerBatch(ArrayList l, int minZoom) : this(l, minZoom, -1) {
			}
			public MarkerBatch(ArrayList l, int minZoom, int maxZoom) {
				markers = l;
				this.minZoom = minZoom;
				this.maxZoom = maxZoom;
			}

			public ArrayList markers;
			public int minZoom;
			public int maxZoom;
		}

		/*	Markers */
		private ArrayList markerBatches = new ArrayList();
		public void addMarkers(ArrayList l) {
			addMarkers(l, 0);
		}
		public void addMarkers(ArrayList l, int minZoom) {
			addMarkers(l, minZoom, -1);
		}
		public void addMarkers(ArrayList l, int minZoom, int maxZoom) {
			markerBatches.Add(new MarkerBatch(l, minZoom, maxZoom));
		}

		public void addMarker(Marker m) {
			addMarker(m, 0);
		}
		public void addMarker(Marker m, int minZoom) {
			addMarker(m, minZoom, -1);
		}
		public void addMarker(Marker m, int minZoom, int maxZoom) {
			bool found = false;
			foreach(MarkerBatch b in markerBatches) {
				if(b.minZoom == minZoom) {
					found = true;
					b.markers.Add(m);
				}
			}

			if(!found) {
				ArrayList mlist = new ArrayList();
				mlist.Add(m);
				markerBatches.Add(new MarkerBatch(mlist, minZoom, maxZoom));
			}
		}

		public ArrayList getMarkerBatches() {
			return markerBatches;
		}

		public void refresh() {
			throw new NotImplementedException("refresh is not implemented at compile time in C# google maps");
		}

		public int getMarkerCount(int zoom) {
			int count = 0;
			foreach(MarkerBatch b in markerBatches) {
				if(b != null && zoom >= b.minZoom && (b.maxZoom == -1 || zoom <= b.maxZoom)) {
					count += b.markers.Count;
				}
			}
			return count;
		}

	}

	public class MarkerManagerOptions {
		public MarkerManagerOptions() {
			borderPadding = -1;
			maxZoom = -1;
			trackMarkers = false;
		}

		public int borderPadding;
		public int maxZoom;
		public bool trackMarkers;
	}
	
	public class InfoWindow {
		public InfoWindow() {
			throw new NotImplementedException("InfoWindow is not implemented at compile time in C# google maps");
		}
	}

	public class InfoWindowOptions {
		public InfoWindowOptions() {
			throw new NotImplementedException("InfoWindowOptions is not implemented at compile time in C# google maps");
		}
	}

	public class InfoWindowContent {
		public InfoWindowContent(string content) {
			this.content = content;
		}

		private string content;

		public string getContent() {
			return content;
		}
	}

	public class InfoWindowTab {
		public InfoWindowTab(string label, string content) {
			this.label = label;
			this.content = content;
		}

		private string label;
		private string content;

		public string getLabel() {
			return label;
		}

		public string getContent() {
			return content;
		}
	}

	public class EventHandler {
		public EventHandler() {
		}

		private Hashtable events = new Hashtable();
		public void addEvent(string name, string javascript) {
			events.Add(name, javascript);
		}
		public Hashtable getEvents() {
			return events;
		}
	}

	public class Event {
		private static ArrayList eventHandlers = new ArrayList();

		public static void addListener(EventHandler handler, string eventName, string javascript) {
			if(!eventHandlers.Contains(handler)) {
				eventHandlers.Add(handler);
			}

			handler.addEvent(eventName, javascript);
		}

		public static ArrayList getEventHandlers() {
			return eventHandlers;
		}
	}
	

	public class Point {
		public Point(int x, int y) {
			this.x = x;
			this.y = y;
		}

		public int x;
		public int y;

		public bool equals(Point other) {
			bool ret = false;
			if(this.x == other.x && this.y == other.y)
				ret = true;
			return ret;
		}

		public override string ToString() {
			return x + "," + y;
		}

		public string toString() {
			return ToString();
		}
	}

	public class Size {
		public Size(int width, int height) {
			this.width = width;
			this.height = height;
		}

		public int width;
		public int height;

		public bool equals(Size other) {
			bool ret = false;
			if(this.width == other.width && this.height == other.height)
				ret = true;
			return ret;
		}

		public override string ToString() {
			return width + "," + height;
		}

		public string toString() {
			return ToString();
		}
	}

	public class Icon {
		public Icon() : this(G_DEFAULT_ICON) {
		}
		public Icon(Icon copy) {
			if(copy != G_DEFAULT_ICON) {
				throw new NotImplementedException("Icon copying is not yet available in C# google maps");
			}
		}
		public Icon(Icon copy, string image) {
			if(image != "") {
				this.image = image;
			}

			if(copy != G_DEFAULT_ICON) {
				throw new NotImplementedException("Icon copying is not yet available in C# google maps");
			}
		}

		public const Icon G_DEFAULT_ICON = null;

		public string image;
		public string shadow;
		public Size iconSize;
		public Size shadowSize;
		public Point iconAnchor;
		public Point infoWindowAnchor;
		public string printImage;
		public string mozPrintImage;
		public string printShadow;
		public string transparent;
		public ArrayList imageMap;
	}

	public class LatLngBounds {
		public LatLngBounds() {
			throw new NotImplementedException("LatLngBounds is not implemented at compile time in C# google maps");
		}
	}

	public class ClientGeocoder {
		ClientGeocoder() {
			throw new NotImplementedException("ClientGeocoder is not implemented at compile time in C# google maps");
		}
	}
}

